package schedulo.core.task.provider;

import java.lang.reflect.Constructor;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;

import schedulo.core.task.provider.parameters.ParameterSpec;
import schedulo.core.task.provider.parameters.ParametersException;

public class TaskProviderFactory {

	private static Object sync;
	
	private static Hashtable<String, Class> providerClasses;

	private static Hashtable<String, ParameterSpec[]> providerParameters;

	private LinkedList<TaskProvider> taskProviders;
	
	private LinkedList<TaskProviderFactoryListener> taskProviderFactoryListeners;

	// fill in provider definitions
	static {
		sync = new Object();
		
		providerClasses = new Hashtable<String, Class>();
		providerParameters = new Hashtable<String, ParameterSpec[]>();

		// always put task prvider name in lower case
		try {
			addTaskProviderType("file", FileTaskProvider.class, FileTaskProvider
					.getParametersSpec());
		} catch (TaskProviderFactoryException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	public static String[] getProviderNames() {
		return providerClasses.keySet().toArray(new String[0]);
	}

	public static ParameterSpec[] getProviderParametersSpec(String providerName) {
		return providerParameters.get(providerName);
	}

	static void addTaskProviderType(String providerName,
			Class providerClass, ParameterSpec[] parametersSpec)
			throws TaskProviderFactoryException {
		if (providerName == null) {
			throw new TaskProviderFactoryException("Invalid provider name: "
					+ providerName);
		}
		providerName = providerName.toLowerCase();

		if (providerClasses.containsKey(providerName)) {
			throw new TaskProviderFactoryException(
					"Provider name already defined: " + providerName);
		}

		providerClasses.put(providerName, providerClass);
		if (parametersSpec == null) {
			parametersSpec = new ParameterSpec[0];
		}
		providerParameters.put(providerName, parametersSpec);
	}

	public static TaskProvider getInstance(Spec providerSpec)
			throws TaskProviderFactoryException, ParametersException {

		synchronized (sync) {
			if (providerClasses == null || providerClasses.size() == 0) {
				throw new TaskProviderFactoryException("No provider available");
			}

			String providerName = providerSpec.getName();

			if (!providerClasses.containsKey(providerName)) {
				throw new TaskProviderFactoryException(
						"No provider with name: '" + providerName + "'");
			}
			Class<TaskProvider> taskProviderClass = providerClasses
					.get(providerName);

			Constructor<TaskProvider> constructor;
			try {
				constructor = taskProviderClass
						.getConstructor(new Class[] { Spec.class });
			} catch (Exception e) {
				e.printStackTrace();
				throw new TaskProviderFactoryException(
						"Cannot find valid constructor for: '" + providerName
								+ "'", e);
			}

			// instanciate
			TaskProvider taskProvider = null;
			try {
				taskProvider = (TaskProvider) constructor
						.newInstance(new Spec[] { providerSpec });
			} catch (Exception e) {
				e.printStackTrace();
				throw new TaskProviderFactoryException("Cannot instanciate: '"
						+ providerName + "'", e);
			}

			// now execute parameter spec for the newly instanciated task
			// provider
			ParameterSpec[] params = providerParameters.get(providerName);
			if (params != null) {
				for (int i = 0; i < params.length; i++) {
					params[i].setParameterSpec(taskProvider, providerSpec
							.getParameters());
				}
			}
			
			// no interlocking, the current thread already holds sync, so reacquiring it won't enter deadlock 
			getInstance().addTaskProvider(taskProvider);
			
			return taskProvider;
		}
	}
	
	private static TaskProviderFactory instance;
	
	public static TaskProviderFactory getInstance() {
		if(instance == null) {
			instance = new TaskProviderFactory();
		}
		return instance;
	}
	
	private TaskProviderFactory() {
		taskProviders = new LinkedList<TaskProvider>();
		taskProviderFactoryListeners = new LinkedList<TaskProviderFactoryListener>();
	}
	
	void fireTaskProviderAdded(TaskProvider taskProvider) {
		synchronized(sync) {
			Iterator<TaskProviderFactoryListener>  itTP = taskProviderFactoryListeners.iterator();
			while(itTP.hasNext()) {
				itTP.next().taskProviderAdded(taskProvider);
			}
		}
	}
	
	void fireTaskProviderRemoved(TaskProvider taskProvider) {
		synchronized(sync) {
			Iterator<TaskProviderFactoryListener>  itTP = taskProviderFactoryListeners.iterator();
			while(itTP.hasNext()) {
				itTP.next().taskProviderRemoved(taskProvider);
			}
		}
	}
	
	public LinkedList<TaskProvider> getTaskProviders() {
		synchronized(sync) {
			return new LinkedList<TaskProvider>(taskProviders);
		}
	}
	
	void addTaskProvider(TaskProvider taskProvider) {
		synchronized(sync) {
			taskProviders.add(taskProvider);
			fireTaskProviderAdded(taskProvider);
		}
	}
	
	public void removeTaskProvider(TaskProvider taskProvider) {
		synchronized(sync) {
			taskProviders.remove(taskProvider);
			fireTaskProviderRemoved(taskProvider);
		}
	}
	
	public void addFactoryListener(TaskProviderFactoryListener listener) {
		synchronized(sync) {
			taskProviderFactoryListeners.add(listener);
		}
	}

	public void removeFactoryListener(TaskProviderFactoryListener listener) {
		synchronized(sync) {
			taskProviderFactoryListeners.remove(listener);
		}
	}
}
